package com.leyou.search.search;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.GoodsDTO;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;
    /**
     * 根据传入的spu数据，生成Goods文档对象
     * @param spuDTO
     * @return
     */
    public Goods createGoods(SpuDTO spuDTO) {
        Long spuId = spuDTO.getId();
//        构造All内容 商品名字 分类名字  品牌名字
        String all = spuDTO.getName()+","+spuDTO.getCategoryName()+","+spuDTO.getBrandName();
//        远程调用item服务，获取spuid对应的sku集合
        List<SkuDTO> skuDTOList = itemClient.findSkuListBySpuId(spuId);
        List<Map<String,Object>> skusList = new ArrayList<>();
//        Set<Long> price = new HashSet<>();
        for (SkuDTO skuDTO : skuDTOList) {
            Map<String,Object> map = new HashMap();
            map.put("skuId",skuDTO.getId());
            map.put("title",skuDTO.getTitle());
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(),","));
            map.put("price",skuDTO.getPrice());
//            price.add(skuDTO.getPrice());
            skusList.add(map);
        }
//        构造price集合
        Set<Long> price = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());
//        把skusList集合转json字符串
        String skus = JsonUtils.toString(skusList);
//        远程调用，根据分类id，和用于搜索的状态，获取规格参数的名字
        List<SpecParamDTO> specParamDTOList = itemClient.findSpecParamListByCondition(spuDTO.getCid3(), null, true);
//        远程调用item，根据spuid，查询spudetail表，获取规格参数的值
        SpuDetailDTO spuDetailDTO = itemClient.findSpuDetailBySpuId(spuId);
//        获取通用的规格参数 id和值的对应json字符串
        String genericSpecJson = spuDetailDTO.getGenericSpec();
//        把通用的规格参数json字符串，转map集合,key - paramId  val- 规格参数的值
        Map<Long, Object> genericMap = JsonUtils.toMap(genericSpecJson, Long.class, Object.class);
//        获取特有规格参数 id和值的对应json字符串
        String specialSpecJson = spuDetailDTO.getSpecialSpec();
        Map<Long,List<String>> specialMap = JsonUtils.nativeRead(specialSpecJson, new TypeReference<Map<Long, List<String>>>() {});
//        规格参数名字和值的最终对应  key - 规格参数的名字 val - 规格参数的值
        Map<String,Object> specs = new HashMap<>();
//        遍历规格参数名字的集合，获取规格参数id，获取规格参数的名字
        for (SpecParamDTO specParamDTO : specParamDTOList) {
            Long paramId = specParamDTO.getId();
            String paramName = specParamDTO.getName();
            Object val = null;
//            判断规格参数是否为通用
            if(specParamDTO.getGeneric()){
//                通用参数
                val = genericMap.get(paramId);
            }else{
//                特有参数
                val = specialMap.get(paramId);
            }
//           判断是否是数字类型
            if(specParamDTO.getIsNumeric()){
                // 是数字类型，分段
                val = chooseSegment(val, specParamDTO);
            }
            specs.put(paramName,val);
        }
//        构造Goods对象
        Goods goods = new Goods();
        goods.setId(spuId);
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setAll(all);
        goods.setSkus(skus);//json字符串
        goods.setPrice(price);
        goods.setSpecs(specs);
        return goods;
    }


    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    @Autowired
    private ElasticsearchTemplate esTemplate;
    /**
     * 关键词分页查询
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> search(SearchRequest request) {
//        获取用户的关键词
        String key = request.getKey();
        if(StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
//        构造原生查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//        设置返回的列
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"},null));
//        设置关键词
//        queryBuilder.withQuery(QueryBuilders.matchQuery("all",key).operator(Operator.AND));
        queryBuilder.withQuery(basicQuery(request));
//        设置分页条件
//        分页的当前页码，从0开始
        Integer page = request.getPage()-1;
        Pageable pageable = PageRequest.of(page,request.getSize());
        queryBuilder.withPageable(pageable);
//        使用ElasticsearchTemplate 把搜索条件querybuilder发送到es的服务端
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
//        操作返回数据，获取集合
        List<Goods> goodsList = aggregatedPage.getContent();
        if(CollectionUtils.isEmpty(goodsList)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        List<GoodsDTO> goodsDTOList = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);

        return new PageResult<GoodsDTO>(goodsDTOList,Long.valueOf(String.valueOf(aggregatedPage.getTotalPages())),aggregatedPage.getTotalElements());
    }

    /**
     * 查询过滤项
     * 前提是根据关键词查询出结果，针对结果使用es的聚合操作
     * @param request
     * @return
     */
    public Map<String, List<?>> searchFilter(SearchRequest request) {
//        获取用户输入的内容
        String key = request.getKey();
        if(StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        Map<String, List<?>> filter = new LinkedHashMap<>();
//        构造原生查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//        设置返回字段
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
//        设置查询条件
//        queryBuilder.withQuery(QueryBuilders.matchQuery("all",key).operator(Operator.AND));
        queryBuilder.withQuery(basicQuery(request));
//        设置分页内容
        queryBuilder.withPageable(PageRequest.of(0,1));
//        添加分类聚合条件
        String categoryAggName = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("categoryId"));
//        添加品牌聚合条件
        String brandAggName = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
//        把查询和聚合的内容发送到es服务端
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
//        获取结果
        Aggregations aggregations = aggregatedPage.getAggregations();
//        处理聚合的内容,get默认返回接口对象，接口中的方法少，我们应该使用实现类
        LongTerms categoryAggs = aggregations.get(categoryAggName);
//        获取分类聚合桶的内容
        List<LongTerms.Bucket> buckets = categoryAggs.getBuckets();
//        获取桶内所有的值
        List<Long> cids = new ArrayList<>();
        for (LongTerms.Bucket bucket : buckets) {
            long categoryId = bucket.getKeyAsNumber().longValue();
            cids.add(categoryId);
        }
//        获取品牌聚合桶的内容
        LongTerms brandAggs = aggregations.get(brandAggName);
//        获取桶内的所有值
        List<Long> brandIds = brandAggs.getBuckets().
                stream().
                map(LongTerms.Bucket::getKeyAsNumber).
                map(Number::longValue).
                collect(Collectors.toList());

//        处理分类 品牌 的聚合结果，从id 查询出对象
        handlerCategoryAggs(cids,filter);
        handlerBrandAggs(brandIds,filter);
//        当前搜索结果只有一个分类，可以进行规格参数的聚合操作
        if(!CollectionUtils.isEmpty(cids) && cids.size()==1){
            handlerSpecsAggs(request,cids.get(0),filter);
        }
        return filter;
    }

    /**
     * 对规格参数进行聚合操作
     * @param request
     * @param cid
     * @param filter
     */
    private void handlerSpecsAggs(SearchRequest request, Long cid, Map<String, List<?>> filter) {
        String key = request.getKey();
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //        设置返回字段
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
//        设置查询条件,使用bool组合查询
//        queryBuilder.withQuery(QueryBuilders.matchQuery("all",key).operator(Operator.AND));
        queryBuilder.withQuery(basicQuery(request));
//        设置分页内容
        queryBuilder.withPageable(PageRequest.of(0,1));
//        远程调用，查询分类对应的 用于搜索的规格参数内容
        List<SpecParamDTO> specParamDTOList = itemClient.findSpecParamListByCondition(cid, null, true);
        if(CollectionUtils.isEmpty(specParamDTOList)){
            return ;
        }
        for (SpecParamDTO specParamDTO : specParamDTOList) {
            String paramName = specParamDTO.getName();
            String fieldName = "specs."+paramName;
            //        添加规格参数的聚合条件
            queryBuilder.addAggregation(AggregationBuilders.terms(paramName).field(fieldName));
        }
//        把查询和聚合的内容发送到es服务端
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

//      获取聚合结果
        Aggregations aggregations = aggregatedPage.getAggregations();
        for (SpecParamDTO specParamDTO : specParamDTOList) {
//            聚合的名字
            String paramName = specParamDTO.getName();
            StringTerms terms = aggregations.get(paramName);
            List<String> specsList = terms.
                    getBuckets().
                    stream().
                    map(StringTerms.Bucket::getKeyAsString).
                    filter(StringUtils::isNotBlank). // 只收集内容不为空的值
                    collect(Collectors.toList());
            filter.put(paramName,specsList);
        }
    }

    private void handlerBrandAggs(List<Long> brandIds, Map<String, List<?>> filter) {
        List<BrandDTO> brandDTOList = itemClient.findBrandListByIds(brandIds);
        filter.put("品牌",brandDTOList);
    }


    /**
     * 远程调用处理分类聚合结果，返回分类对象集合
     * @param cids
     * @param filter
     */
    private void handlerCategoryAggs(List<Long> cids, Map<String, List<?>> filter) {
        List<CategoryDTO> categoryDTOList = itemClient.findCategoryListByIds(cids);
        filter.put("分类",categoryDTOList);
    }

    /**
     * 基础查询，包含bool组合 must + filter
     * @param request
     * @return
     */
    private BoolQueryBuilder basicQuery(SearchRequest request){
        String key = request.getKey();
//        构建bool组合查询对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//        设置must条件，包含关键词查询
        boolQuery.must(QueryBuilders.matchQuery("all",key).operator(Operator.AND));

//        获取用户的选择过滤项
        Map<String, String> filter = request.getFilter();
        if(!CollectionUtils.isEmpty(filter)){
//            说明用户选择了过滤内容
            for (String filterName : filter.keySet()) {
                String filterVaule = filter.get(filterName);
//                es中的列名
                String fieldName = "specs."+filterName;
                if(filterName.equals("分类")){
                    fieldName = "categoryId";
                }
                if(filterName.equals("品牌")){
                    fieldName = "brandId";
                }
//                添加bool查询的filter项
                boolQuery.filter(QueryBuilders.termQuery(fieldName,filterVaule));
            }
        }
        return boolQuery;
    }


    @Autowired
    private GoodsRepository repository;
    /**
     * 创建索引
     * @param spuId
     */
    public void createIndex(Long spuId) {
//        查询spuDTO的数据
        SpuDTO spuDTO = itemClient.findSpuById(spuId);
//        获取分类名字
        List<CategoryDTO> categoryDTOList = itemClient.findCategoryListByIds(spuDTO.getCategoryIds());
//        获取分类的名字 1 2 3 级的名字
        String categoryName = categoryDTOList.stream().map(CategoryDTO::getName).collect(Collectors.joining("/"));
        spuDTO.setCategoryName(categoryName);
//        获取品牌的名字
        BrandDTO brandDTO = itemClient.findBrandById(spuDTO.getBrandId());
        spuDTO.setBrandName(brandDTO.getName());
//        生成goods对象
        Goods goods = this.createGoods(spuDTO);
//        把Goods对象写入es
        repository.save(goods);
    }

    /**
     * 删除索引
     * @param spuId
     */
    public void removeIndex(Long spuId) {
        repository.deleteById(spuId);
    }
}
